package com.griddroid.nodes;

import java.io.IOException;
import java.util.HashMap;
import java.util.Vector;
import java.util.concurrent.LinkedBlockingQueue;
import android.os.Message;
import com.griddroid.configuration.GridConfig;
import com.griddroid.configuration.GridServerConfig;
import com.griddroid.serverdaemons.ClientDaemon;
import com.griddroid.serverdaemons.MergerDaemon;
import com.griddroid.serverdaemons.NodeManagerDaemon;
import com.griddroid.serverdaemons.SplitterDaemon;
import com.griddroid.serverdaemons.WorkerDaemon;
import com.griddroid.works.WorkRequest;
import com.griddroid.works.WorkResponse;



/**
 * This class represents a Grid Server Node. It extends the abstract class
 * {@link GridNode GridNode} to gain the main functionality of.
 * 
 * @author Charalampos Mavroforakis
 * @author Thomas Ailianos
 * @author Michail Michailidis
 */
public class GridServer extends GridNode
{
	private NodeManagerDaemon nodeManager;
	private MergerDaemon mergerDaemon;
	private SplitterDaemon splitterDaemon;
	private ClientDaemon clientDaemon;
	private WorkerDaemon workerDaemon;
	private Vector<WorkRequest> workRequests;
	private Vector<WorkResponse> workResponses;
	private LinkedBlockingQueue<WorkRequest> workFragmentRequests;
	private Vector<WorkResponse> workFragmentResponses;
	private HashMap<Integer, Boolean> portMap; // true means free port

	
	/**
	 * Constructs a GridServer Object. It initializes all collections it has.
	 * It has 4 collections:workRequests,workResponses,workFragmentRequests,workFragmentResponses. Each is used to collect all objects of the same type.
	 * Moreover there is a collection named portMap that is initialized with a port range according to the {@link GridServerConfig GridServerConfig}.
	 */
	public GridServer()
	{
		super();
		this.type = NodeType.SERVER;
		workRequests = new Vector<WorkRequest>();
		workResponses = new Vector<WorkResponse>();
		workFragmentRequests = new LinkedBlockingQueue<WorkRequest>();
		workFragmentResponses = new Vector<WorkResponse>();
		portMap = new HashMap<Integer, Boolean>();
		for (int i = GridServerConfig.SERVERMINPORT; i <= GridServerConfig.SERVERMAXPORT; i++)
		{
			portMap.put(i, true);
		}
	}

	@Override
	/**
	 * It starts GridServer and all its daemons. It has 5 daemons.
	 * NodeManager response for the entry of new nodes(Servers or workers) into the grid.
	 * Client daemon responsible to communicate with all clients and pass their {@link WorkRequest WorkRequests}
	 * Splitter daemon responsible for the splitting of work requests into fragments.
	 * Worker daemon responsible for the forwarding of work fragment request to the workers and the work fragment responses from the workers to the server
	 * Merger daemon responsible for the merge of work fragment responses to work responses.
	 * 
	 */
	public void start()
	{
		Thread managerThread;
		try
		{
			NodeManagerDaemon manager = new NodeManagerDaemon(this);
			managerThread = new Thread(manager);
			managerThread.start();
			this.log("Info", "Node manager started");
		}
		catch (IOException e)
		{
			this.log("Error", "Could not start node manager." + e.getMessage());
		}
		Thread clientThread;

		ClientDaemon client = new ClientDaemon(this);
		clientThread = new Thread(client);
		clientThread.start();
		this.log("Info", "Client daemon started");

		WorkerDaemon worker = new WorkerDaemon(this);
		Thread workerThread = new Thread(worker);
		workerThread.start();
		this.log("Info", "Worker daemon started");

		Thread splitterThread;
		SplitterDaemon splitter = new SplitterDaemon(this);
		splitterThread = new Thread(splitter);
		splitterThread.start();
		this.log("Info", "Splitter daemon started");
		Thread mergeThread;
		MergerDaemon merger = new MergerDaemon(this);
		mergeThread = new Thread(merger);
		mergeThread.start();
		this.log("Info", "Merger daemon started");

	}

	/**
	 * Main function that creates a GridServer node and startes it
	 * @param args
	 */
	public static void main(String args[])
	{
		GridServer server = new GridServer();
		server.start();
	}

	/**
	 * Returns the node manager daemon
	 * @return
	 * 		the node manager daemon
	 */
	public NodeManagerDaemon getNodeManager()
	{
		return nodeManager;
	}

	/**
	 * Sets the node manager daemon
	 * @param nodeManager
	 * 			Node manager of the server
	 */
	public void setNodeManager(NodeManagerDaemon nodeManager)
	{
		this.nodeManager = nodeManager;
	}

	/**
	 * Sets the client daemon
	 * @param clientDaemon
	 * 			client daemon of the server
	 */
	public void setClientDaemon(ClientDaemon clientDaemon)
	{
		this.clientDaemon = clientDaemon;
	}

	/**
	 * Sets the worker daemon of the server
	 * @param workerDaemon
	 * 		{@link WorkerDaemon WorkerDaemon} of the server
	 */
	public void setWorkerDaemon(WorkerDaemon workerDaemon)
	{
		this.workerDaemon = workerDaemon;
	}

	/**
	 * Get function for the client daemon 
	 * @return
	 * 		{@link ClientDaemon ClientDaemon} of the server
	 */
	public ClientDaemon getClientDaemon()
	{
		return this.clientDaemon;
	}

	/**
	 * Get function for the worker daemon
	 * @return
	 */
	public WorkerDaemon getWorkerDaemon()
	{
		return workerDaemon;
	}

	/**
	 * Return the collection containing the {@link WorkRequest WorkRequests} saved into the server
	 * @return
	 */
	public Vector<WorkRequest> getWorkRequests()
	{
		return workRequests;
	}

	/**
	 * Returns the collection containing the {@link WorkResponse WorkResponses} saved into the server
	 * @return
	 */
	public Vector<WorkResponse> getWorkResponses()
	{
		return workResponses;
	}

	/**
	 * Return the collection containing the work fragments actually smaller {@link WorkRequest WorkRequests}
	 * @return
	 */
	public LinkedBlockingQueue<WorkRequest> getWorkFragmentRequests()
	{
		return workFragmentRequests;
	}

	/**
	 * Returns the collection containing the work fragment responses saved into the server. Actually they are smaller {@link WorkResponse WorkResponses}
	 * @return
	 */
	public Vector<WorkResponse> getWorkFragmentResponses()
	{
		return workFragmentResponses;
	}

	@Override
	/**
	 * Closes the server after closing all of its daemons
	 */
	public void close()
	{
		if (this.clientDaemon != null)
		{
			this.clientDaemon.close();
			this.clientDaemon = null;
		}

		if (this.workerDaemon != null)
		{
			this.workerDaemon.close();
			this.workerDaemon = null;
		}

		if (this.mergerDaemon != null)
		{
			this.mergerDaemon.close();
			this.mergerDaemon = null;
		}

		if (this.splitterDaemon != null)
		{
			this.splitterDaemon.close();
			this.splitterDaemon = null;
		}

		if (this.nodeManager != null)
		{
			this.nodeManager.close();
			this.nodeManager = null;
		}
		Message m = Message.obtain();
		m.what = GridConfig.CLOSE;
		this.messageHandler.sendMessage(m);

	}

	/**
	 * Returns a free port for the port range configured into the {@link GridServerConfig GridServerConfig}
	 * @return
	 */
	public synchronized int getFreePort()
	{
		for (int i = GridServerConfig.SERVERMINPORT; i <= GridServerConfig.SERVERMAXPORT; i++)
		{
			if (portMap.get(i))
			{
				portMap.put(i, false);
				return i;
			}
		}
		return -1;
	}

	/**
	 * Releases a port not used more.
	 * @param p
	 * 	port number
	 */
	public synchronized void freePort(int p)
	{
		portMap.put(p, true);
	}

	/**
	 * Returns a string with all available ports and their status.
	 * @return
	 * 		string with all ports.
	 */
	public String printPorts()
	{
		String s = "";
		for (int p : portMap.keySet())
		{
			s += "" + p + " : " + portMap.get(p).toString() + "\n";
		}
		return s;
	}
}
